﻿extern alias sl;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Samples.VisualStudio.GeneratorSample;
using System.CodeDom.Compiler;
using System.CodeDom;
using System.IO;
using System.Xaml;
using System.Xml;
using System.Diagnostics;
using NeuroSpeech.UIAtoms.Generator;
using Microsoft.VisualStudio.Shell.Design;
using System.ComponentModel.Design;
using Microsoft.VisualStudio.Shell;
using System.Reflection;
using NeuroSpeech.XamlExtensions;

namespace NeuroSpeechInc.AtomXamlGenerator
{
    public abstract class BaseXamlGenerator : BaseCodeGeneratorWithSite
    {

        #region GenerateCode
        protected override byte[] GenerateCode(string inputFileContent)
        {
            CodeDomProvider provider = GetCodeProvider();

            try
            {

                //Create the CodeCompileUnit from the passed-in XML file
                CodeCompileUnit compileUnit = CreateCodeCompileUnit(inputFileContent, FileNameSpace);

                if (this.CodeGeneratorProgress != null)
                {
                    //Report that we are 1/2 done
                    this.CodeGeneratorProgress.Progress(50, 100);
                }

                using (StringWriter writer = new StringWriter(new StringBuilder()))
                {
                    CodeGeneratorOptions options = new CodeGeneratorOptions();
                    options.BlankLinesBetweenMembers = false;
                    options.BracingStyle = "C";

                    //Generate the code
                    provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);

                    if (this.CodeGeneratorProgress != null)
                    {
                        //Report that we are done
                        this.CodeGeneratorProgress.Progress(100, 100);
                    }
                    writer.Flush();

                    //Get the Encoding used by the writer. We're getting the WindowsCodePage encoding, 
                    //which may not work with all languages
                    Encoding enc = Encoding.GetEncoding(writer.Encoding.WindowsCodePage);

                    //Get the preamble (byte-order mark) for our encoding
                    byte[] preamble = enc.GetPreamble();
                    int preambleLength = preamble.Length;

                    //Convert the writer contents to a byte array
                    byte[] body = enc.GetBytes(writer.ToString());

                    //Prepend the preamble to body (store result in resized preamble array)
                    Array.Resize<byte>(ref preamble, preambleLength + body.Length);
                    Array.Copy(body, 0, preamble, preambleLength, body.Length);

                    //Return the combined byte array
                    return preamble;
                }
            }
            catch (Exception e)
            {
                this.GeneratorError(4, e.ToString(), 1, 1);
                //Returning null signifies that generation has failed
                return null;
            }
        }

        #endregion

        private bool IsProjectSilverlight;

        protected virtual CodeCompileUnit CreateCodeCompileUnit(
            string inputFileContent, 
            string FileNameSpace)
        {

            WPFXamlWriter writer = new WPFXamlWriter(GetSchemaContext());
            //writer.IsProjectSilverlight = ProjectHasExtender(this.GetProject(), "SilverlightProject");
            EnvDTE.Property p = GetProject().Properties.Item("TargetFrameworkMoniker");
            if (p != null) {
                string fn = p.Value.ToString().ToLower();
                writer.IsProjectSilverlight = fn.Contains("silverlight");
                IsProjectSilverlight = writer.IsProjectSilverlight;
            }
            //writer.IsProjectSilverlight = this.GetProject().Kind == VslangProj90.si
            CodeCompileUnit unit = new CodeCompileUnit();
            CodeNamespace cn = writer.Parse(new StringReader(inputFileContent));
            unit.Namespaces.Add(cn);
            if (writer.Warnings.Length>0)
                this.GeneratorWarning(0, writer.Warnings.ToString(), 0, 0);
            return unit;
        }

        #region private object ProjectHasExtender(EnvDTE.Project project,string p)
        public bool ProjectHasExtender(EnvDTE.Project proj, string extenderName)
        {
            bool result = false;
            object[] extenderNames;

            try
            {
                // We could use proj.Extender(extenderName) but it causes an exception if not present and 
                // therefore it can cause performance problems if called multiple times. We use instead:

                extenderNames = (object[])proj.ExtenderNames;

                if (extenderNames.Length > 0)
                {
                    foreach (object extenderNameObject in extenderNames)
                    {
                        if (extenderName.ToString() == extenderName)
                        {
                            result = true;
                            break;
                        }
                    }
                }
            }
            catch
            {
                // Ignore
            }
            return result;
        }
        #endregion


        private T GetOleService<T>() 
            where T:class
        {
            T st = SP.GetService(typeof(T)) as T;
            Debug.Assert(st!=null,"Could not found " + typeof(T).FullName + " in Ole Service Provider");
            return st;
        }

        private IServiceProvider SP;

        #region private XamlSchemaContext GetSchemaContext()
        private XamlSchemaContext GetSchemaContext()
        {
            if (SP == null)
            {
                Microsoft.VisualStudio.OLE.Interop.IServiceProvider oleSP = GetProject().DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
                SP = new ServiceProvider(oleSP);
            }

            DynamicTypeService dts = GetOleService<DynamicTypeService>();

            Microsoft.VisualStudio.Shell.Interop.IVsSolution sln =
                GetOleService<Microsoft.VisualStudio.Shell.Interop.IVsSolution>();


            Microsoft.VisualStudio.Shell.Interop.IVsHierarchy hier = null;
            sln.GetProjectOfUniqueName(GetProject().UniqueName, out hier);

            Debug.Assert(hier != null, "Could not load IVsHierarchy");

            ITypeResolutionService rs = dts.GetTypeResolutionService(hier);

            Debug.Assert(rs!=null,"Could not load ITypeResolutionService");

            List<Assembly> refAssemblies = LoadAssemblies(GetVSProject().References);

            return new CodeSchemaContext(refAssemblies, GetProject(), rs);
        }

        private List<Assembly> LoadAssemblies(VSLangProj.References references)
        {

            List<Assembly> refAssemblies = new List<Assembly>();

            string assemblyName = null;
            string assemblyPath = null;
            foreach (VSLangProj.Reference iref in references)
            {

                if (iref.StrongName)
                {
                    //System.Configuration, Version=2.0.0.0,
                    //Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A
                    assemblyName = iref.Identity +
                        ", Version=" + iref.Version +
                        ", Culture=" + (string.IsNullOrEmpty(iref.Culture) ?
                        "neutral" : iref.Culture) +
                        ", PublicKeyToken=" + iref.PublicKeyToken;
                    Assembly a = Assembly.Load(assemblyName);
                    refAssemblies.Add(a);
                }
                else
                {
                    assemblyPath = iref.Path;
                    Assembly a = Assembly.LoadFrom(assemblyPath);
                    refAssemblies.Add(a);
                }
            }


            Assembly aex;
            if (IsProjectSilverlight)
                aex = typeof(sl::NeuroSpeech.XamlExtensions.Ref).Assembly;
            else
                aex = typeof(NeuroSpeech.XamlExtensions.Ref).Assembly;

            if (!refAssemblies.Any(x => x.FullName == aex.FullName)) {
                refAssemblies.Add(aex);
                references.Add(aex.Location);
            }

            return refAssemblies;
        }

        #endregion

        public class CodeSchemaContext : XamlSchemaContext {

            private EnvDTE.Project project;
            private EnvDTE.CodeModel codeModel;
            ITypeResolutionService typeService;

            public CodeSchemaContext(List<Assembly> refAssemblies, EnvDTE.Project project, ITypeResolutionService service)
                : base(refAssemblies)
            {
                this.project = project;
                this.codeModel = project.CodeModel;
                this.typeService = service;
            
                
            }


            private readonly static string Extension = "Extension";

            #region protected override XamlType  GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
            protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
            {


                XamlType type = base.GetXamlType(xamlNamespace, name, typeArguments);

                if (type == null && name.EndsWith(Extension)) {
                    name = name.Substring(0,name.Length- Extension.Length);
                    type = base.GetXamlType(xamlNamespace,name,typeArguments);
                }

                if (type == null || type.UnderlyingType==null) {
                    type = new XamlType(GetNewType(xamlNamespace, name, typeArguments), this);
                    return type;
                }
                return type;
            }

            #endregion

            


            private static readonly string clrNamespace = "clr-namespace:";


            #region private Type GetNewType(string xamlNamespace,string name,XamlType[] typeArguments)
            private Type GetNewType(string xamlNamespace, string name, XamlType[] typeArguments)
            {
                string ns = xamlNamespace;
                if (ns.StartsWith(clrNamespace))
                {
                    ns = ns.Substring(clrNamespace.Length);
                }
                else {
                    //if (xamlNamespace == "http://ns.neurospeech.com/xaml")
                    //{
                    //    Assembly asm = typeof(XamlTypeGenerator).Assembly;
                    //    return asm.GetType(name, true);
                    //}
                    throw new TypeAccessException("Could not load {"+xamlNamespace+"}" + name);
                }
                string fullName = ns + "." + name;

                EnvDTE.CodeType codeType =  codeModel.CodeTypeFromFullName(fullName);
                if (codeType != null) {
                    return typeService.GetType(codeType.FullName, true);
                }


                Debug.Assert(false, "CodeType was not loaded for " + fullName);
                return null;
            }
            #endregion

        }


    }
}
